﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.ExtendedReflection.Metadata;
using Microsoft.ExtendedReflection.Utilities;
using PexMe.ComponentModel;
using PexMe.Core;
using PexMe.ComponentModel.Hardcoded;
using System.Reflection.Emit;

namespace PexMeHelper
{
    /// <summary>
    /// Collects various statistics from the assembly file
    /// </summary>
    public class StatCollector
    {
        public static bool RandoopStats = false;
        static int numClasses = 0;
        static int numMethods = 0;
        static int numTests = 0;
        static double avgSequenceLength = 0;
        static int maxSequenceLength = 0;
        static List<int> allSequenceLengths = new List<int>();
        static string maxSequenceLengthTest;

        public static void CollectStats(string assemblyname)
        {
            PreDefinedGenericClasses.LoadPredefinedGenericClasses(null, assemblyname);
            
            AssemblyEx assembly;
            ReflectionHelper.TryLoadAssemblyEx(assemblyname, out assembly);

            foreach (var tdef in assembly.TypeDefinitions)
            {
                if (RandoopStats && !tdef.ShortName.Contains("RandoopTest"))
                    continue;
                
                bool bTestClass = false;
                if (TypeAnalyzer.IsAPexClass(tdef) || TypeAnalyzer.IsATestClass(tdef))
                {
                    bTestClass = true;
                }
                else
                    numClasses++;

                if (!bTestClass)
                {
                    foreach (var property in tdef.DeclaredProperties)
                    {
                        if (property.Setter != null)
                            numMethods++;

                        if (property.Getter != null)
                            numMethods++;
                    }
                }
                
                foreach (var smdef in tdef.DeclaredStaticMethods)
                {
                    //ignore all pexmethods
                    if (TypeAnalyzer.IsAPexMethod(smdef))
                        continue;

                    if (TypeAnalyzer.IsATestMethod(smdef))
                    {
                        numTests++;
                        CollectSequenceStats(smdef, tdef);
                    }
                    else
                    {
                        if (RandoopStats && smdef.ShortName.Contains("RandoopTest"))
                        {
                            numTests++;
                            CollectSequenceStats(smdef, tdef);
                        }
                        else
                            numMethods++;
                    }
                }

                foreach (var smdef in tdef.DeclaredInstanceMethods)
                {
                    //ignore all pexmethods
                    if (TypeAnalyzer.IsAPexMethod(smdef))
                        continue;

                    if (TypeAnalyzer.IsATestMethod(smdef))
                    {
                        numTests++;
                        CollectSequenceStats(smdef, tdef);
                    }
                    else
                    {
                        numMethods++;
                    }
                }
            }


            Console.WriteLine("Number of classes : " + numClasses);
            Console.WriteLine("Number of methods : " + numMethods);
            Console.WriteLine("Number of test methods: " + numTests);
            avgSequenceLength = ((avgSequenceLength == 0) ? 0 : (avgSequenceLength / allSequenceLengths.Count));
            Console.WriteLine("Average sequence length: " + avgSequenceLength);
            Console.WriteLine("Maximum sequence length: " + maxSequenceLength + " Testname: (" + maxSequenceLengthTest + ")");

            //Computing standard deviation
            double squaredSum = 0;
            foreach (int origleng in allSequenceLengths)
            {
                squaredSum += Math.Pow(origleng - avgSequenceLength, 2);
            }
            var sd = Math.Sqrt(squaredSum / (allSequenceLengths.Count - 1));
            Console.WriteLine("Stadard deviation: " + sd);
        }

        private static void CollectSequenceStats(MethodDefinition smdef, TypeDefinition tdef)
        {
            try
            {
                int seqLength = 0;
                Method method = smdef.Instantiate(MethodOrFieldAnalyzer.GetGenericTypeParameters(null, tdef), 
                    MethodOrFieldAnalyzer.GetGenericMethodParameters(null, smdef));                                              

                MethodBodyEx body;
                if (!method.TryGetBody(out body) || !body.HasInstructions)
                {
                    return;
                }                
                int offset = 0;
                Instruction instruction;
                OpCode prevOpcode = OpCodes.Nop;
                
                while (body.TryGetInstruction(offset, out instruction))
                {                    
                    OpCode opCode = instruction.OpCode;
                    if (opCode == OpCodes.Call || opCode == OpCodes.Callvirt)
                    {                       
                        var fullname = instruction.Method.FullName;

                        if (RandoopStats)
                        {
                            TypeEx declTypeEx;
                            if (instruction.Method.TryGetDeclaringType(out declTypeEx) && declTypeEx.FullName.Contains("Exception"))
                            {
                                offset = instruction.NextOffset;
                                continue;
                            }
                        }

                        //ignore the assert method calls and console writeline methods
                        if (!fullname.Contains("Microsoft.VisualStudio.TestTools.UnitTesting.Assert")
                            && !fullname.Contains("System.Console.WriteLine"))
                        {
                            if (!RandoopStats)
                            {
                                //Neither Seeker nor Pex never uses pure observer methods as a part of sequence
                                //they can be filtered out, since they would be used only in assertions
                                if (instruction.Method.ShortName.Contains("get_"))
                                {
                                    offset = instruction.NextOffset;
                                    continue;
                                }
                            }

                            seqLength++;
                        }
                    }
                    else if (opCode == OpCodes.Newobj || opCode == OpCodes.Newarr)
                    {
                        seqLength++; 
                    }

                    offset = instruction.NextOffset;
                }

                //Some issue with Randoop, where we get two calls, even if there are none
                if (RandoopStats && seqLength > 0)
                    seqLength = seqLength - 2;

                if (maxSequenceLength < seqLength)
                {
                    maxSequenceLength = seqLength;
                    maxSequenceLengthTest = smdef.FullName;
                }

                avgSequenceLength += seqLength;
                allSequenceLengths.Add(seqLength);
                return;
            }
            catch (Exception ex)
            {                
            }
        }
    }
}
